package org.stars.daostars.core;

import static org.stars.daostars.core.SqlFragmentType.TYPE_ARRAY;
import static org.stars.daostars.core.SqlFragmentType.TYPE_IF;
import static org.stars.daostars.core.SqlFragmentType.TYPE_SIMPLE;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.daostars.core.conditions.Condition;
import org.stars.daostars.sqlmapper.SqlMapper;
import org.stars.security.UserIdentity;
import org.stars.util.StringUtility;

/**
 * Contiene un pezzo di codice sql. Al suo interno possiamo trovare i parametri.
 * L'esecuzione di tale codice sql pu� essere condizionato da una condizione
 * definito a livello di Dao.
 * 
 * @author Francesco Benincasa (908099)
 * 
 */
/**
 * @author Francesco Benincasa (908099)
 * 
 */
public class SqlFragment implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3544289570772276096L;

	/**
	 * Nel caso di clause di tipo array, esso contiene il parametro da
	 * considerare come parametro
	 */
	protected SqlParameter attribute;

	/**
	 * nome della condizione
	 */
	protected String conditionName;

	/**
	 * parametri della query nel caso in cui la condizione non è stata
	 * rispettata.
	 */
	protected List<SqlParameter> elseParameters;

	/**
	 * codice sql da eseguire nel caso in cui la condizione non è rispettata.
	 */
	protected String elseSql;

	/**
	 * sub query else
	 */
	protected List<SqlFragment> elseSubClauses;

	/**
	 * elenco dei parametri della query
	 */
	protected List<SqlParameter> parameters;

	/**
	 * codice che definisce la query nella sua definizione base.
	 */
	protected String sql;

	/**
	 * sub query
	 */
	protected List<SqlFragment> subClauses;

	/**
	 * tipo di condizione
	 */
	private SqlFragmentType type;

	/**
	 * contiene l'elenco delle variabili utilizzate all'interno dello statement
	 */
	protected List<String> usedVariables;

	/**
	 * 
	 */
	public SqlFragment() {
		this("");
	}

	/**
	 * Costruttore che permette di definire lo statement come array
	 * 
	 * @param paramArray
	 *            se <code>null</code> o <code>""</code> indica che lo
	 *            statement non è di tipo array.
	 */
	public SqlFragment(SqlParameter paramArray) {
		if (paramArray != null) {
			attribute = paramArray;
			setUp(TYPE_ARRAY);
		} else {
			setUp(TYPE_SIMPLE);
		}
	}

	/**
	 * Costruttore che permette di definire lo statement come condizionale o
	 * meno a seconda del fatto che venga definito il nome della condition.
	 * 
	 * @param sConditionName
	 *            se <code>null</code> o <code>""</code> indica che lo
	 *            statement non è condizionato da nessun vincolo.
	 */
	public SqlFragment(String sConditionName) {
		if (StringUtility.nvl(conditionName).length() > 0) {
			conditionName = sConditionName;
			setUp(TYPE_IF);
		} else {
			setUp(TYPE_SIMPLE);
		}
	}

	/**
	 * Aggiunge un parametro all'elenco dei parametri della clausola
	 * 
	 * @param param
	 */
	public void addParameter(SqlParameter param) {
		parameters.add(param);
	}

	/**
	 * Restituisce <code>true</code> nel caso in cui contiene delle sotto
	 * clausole.
	 * 
	 * @return <code>true</code> se contiene sotto clausole.
	 */
	public boolean containsSubClauses() {
		return subClauses.size() > 0;
	}

	/**
	 * Restituisce <code>true</code> nel caso in cui contiene delle sotto
	 * clausole per l'else.
	 * 
	 * @return <code>true</code> se contiene sotto clausole.
	 */
	public boolean containsSubElseClauses() {
		return elseSubClauses.size() > 0;
	}

	/**
	 * Restituisce l'elenco di tutti i parameti utilizzati.
	 * 
	 * @return
	 * 		elenco dei parametri utilizzati
	 */
	public ArrayList<SqlParameter> getAllParameters() {
		ArrayList<SqlParameter> ret=new ArrayList<SqlParameter>();
		if (containsSubClauses())
		{
			for(SqlFragment clause: subClauses)
			{
				ret.addAll(clause.getAllParameters());
			}
		} else {
			ret.addAll(parameters);
			ret.addAll(elseParameters);
		}
		return ret;
		
	}

	/**
	 * @return the attribute
	 */
	public SqlParameter getAttribute() {
		return attribute;
	}

	/**
	 * Getter dell'attributo conditionName.
	 * 
	 * @return the conditionName
	 */
	public String getConditionName() {
		return conditionName;
	}
	
	/**
	 * @return the elseParameters
	 */
	public List<SqlParameter> getElseParameters() {
		return elseParameters;
	}

	/**
	 * @param variables
	 *            variabili della definizione del dao.
	 * @return the elseSql sql di else.
	 */
	public String getElseSql(Map<String, String> variables) {
		if (usedVariables.size() > 0 && variables != null) {
			// sono usate delle variabili e ci sono dei valori
			String temp = elseSql;

			String[] values = new String[usedVariables.size()];

			int i = 0;
			for (String var : usedVariables) {
				values[i] = variables.get(var);
				i++;
			}

			return MessageFormat.format(temp, (Object[]) values);
		} else {
			// ASSERT: non abbiamo variabili associate o non abbiamo ricevuto
			// in input la mappa delle varibili, quindi restituiamo
			// direttamente il codice elseSql.
			return elseSql;
		}
	}

	/**
	 * @return the elseSubClauses
	 */
	public List<SqlFragment> getElseSubClauses() {
		return elseSubClauses;
	}

	/**
	 * Logger
	 * 
	 * @return logger
	 */
	public Log getLog() {
		return LogFactory.getLog(SqlFragment.class);
	}

	/**
	 * Getter dell'attributo parameters.
	 * 
	 * @return the parameters
	 */
	public List<SqlParameter> getParameters() {
		return parameters;
	}

	/**
	 * Getter dell'attributo processed.
	 * 
	 * @return the processed
	 * 
	 * public boolean isProcessed() { return processed; }
	 */

	/**
	 * data una definizione di dao, recupera l'elenco dei parametri di questo
	 * pezzo di codice sql in base anche alle eventuali condizioni che devono
	 * essere soddisfatte
	 * 
	 * @param definition
	 *            dao definition
	 * @param user
	 *            user identity
	 * @param eb
	 *            entity bean con i parametri
	 * @param params
	 *            parametri
	 * @return elenco dei parametri associati a questo pezzo di codice sql
	 * @throws Exception
	 */
	public ArrayList<SqlParameter> getParameters(SqlMapper definition,
			UserIdentity user, Object eb, Object[] params, Map<String, String> variables) throws Exception {
		
		Condition condition;
		if (!containsSubClauses()) {
			
			ArrayList<SqlParameter> ret = new ArrayList<SqlParameter>();

			switch (type) {
			case TYPE_SIMPLE:
				ret.addAll(getParameters());
				break;
			case TYPE_IF:
				if (definition.existsCondition(conditionName)) {
					condition= definition
							.getCondition(conditionName);

					if (condition.isVerified(user, eb, params, variables)) {
						ret.addAll(getParameters());
					} else {
						ret.addAll(getElseParameters());
					}
				} else {
					Log log = getLog();
					log.error("The condition " + conditionName + " does not exist");
				}
				break;
			case TYPE_ARRAY:
				attribute = getAttribute();
				Object[] arrayElement = Helper.getArrayOfValue(attribute, eb,
						params);

				ArrayList<SqlParameter> tempArray = new ArrayList<SqlParameter>();
				SqlParameter tempParam;

				if (arrayElement != null && arrayElement.length > 0) {
					for (int i = 0; i < arrayElement.length; i++) {
						tempParam = new SqlParameter();
						
						tempParam.setAttribute(attribute.getAttribute()+"["+i+"]");
						tempParam.setSimpleAttribute(attribute.getAttribute());												
						
						tempParam.setType(SqlParameterType.TYPE_RESOLVED);
						
						//FIX su enum
						if (arrayElement[i]!=null && arrayElement[i].getClass().isEnum())
						{
							tempParam.setValue(arrayElement[i].toString());
						} else 
						{
							tempParam.setValue(arrayElement[i]);
						}
						
						tempArray.add(tempParam);
					}
					ret.addAll(tempArray);
				} else {
					ret.addAll(getParameters());
				}
				break;
			}

			return ret;
		} else {
			ArrayList<SqlParameter> ret = new ArrayList<SqlParameter>();
			
			switch (type) {
			case TYPE_SIMPLE:
				for (SqlFragment sub : subClauses) {
					ret.addAll(sub.getParameters(definition, user, eb, params, variables));
				}
				break;
			case TYPE_IF:
				if (definition.existsCondition(conditionName)) {
					condition = definition.getCondition(conditionName);

					if (condition.isVerified(user, eb, params, variables)) {
						for (SqlFragment sub : subClauses) {
							ret.addAll(sub.getParameters(definition, user, eb, params, variables));
						}
					} else {
						for (SqlFragment sub : elseSubClauses) {
							ret.addAll(sub.getParameters(definition, user, eb, params, variables));
						}
					}
				} else {
					Log log=getLog();
					log.error("The condition " + conditionName + " does not exist");
				}
				break;
			case TYPE_ARRAY:
				attribute = getAttribute();
				Object[] arrayElement = Helper.getArrayOfValue(attribute, eb,
						params);

				ArrayList<SqlParameter> tempArray = new ArrayList<SqlParameter>();
				SqlParameter tempParam;

				if (arrayElement != null && arrayElement.length > 0) {
					for (int i = 0; i < arrayElement.length; i++) {
						tempParam = new SqlParameter();
						
						tempParam.setAttribute(attribute.getAttribute()+"["+i+"]");
						tempParam.setSimpleAttribute(attribute.getAttribute());
						
						tempParam.setType(SqlParameterType.TYPE_RESOLVED);
						//FIX su enum
						if (arrayElement[i]!=null && arrayElement[i].getClass().isEnum())
						{
							tempParam.setValue(arrayElement[i].toString());
						} else 
						{
							tempParam.setValue(arrayElement[i]);
						}
						tempArray.add(tempParam);
					}
					ret.addAll(tempArray);
				} else {
					for (SqlFragment sub : subClauses) {
						ret.addAll(sub.getParameters(definition, user, eb, params, variables));
					}
				}
				break;
			}
			
			
			return ret;
		}
	}

	/**
	 * Recupera il codice sql associato allo statement. Per fare questo,
	 * considera i parametri e le variabili associate al dao (alla sua istanza e
	 * alla sua definizione).
	 * 
	 * @param definition
	 *            definizione del dao
	 * @param user
	 *            user che sta invocando la query
	 * @param eb
	 *            entity bean che contiene i parametri attibuto della query
	 * @param params
	 *            parametri indicizzati della query
	 * @param variables
	 *            variabili
	 * @return codice sql della query
	 * @throws Exception
	 *             in caso di errore
	 */
	public String getSql(SqlMapper definition, UserIdentity user,
			Object eb, Object[] params, Map<String, String> variables)
			throws Exception {
		Condition condition;		
		
		if (!containsSubClauses()) {						
			String ret = "";

			switch (type) {
			case TYPE_SIMPLE:
				ret = getSql(variables);
				break;
			case TYPE_IF:
				if (definition.existsCondition(conditionName)) {
					condition = definition.getCondition(conditionName);

					if (condition.isVerified(user, eb, params, variables)) {
						ret = getSql(variables);
					} else {
						ret = getElseSql(variables);
					}
				} else {
					Log log = getLog();
					log.error("The condition " + conditionName + " does not exist");
				}
				break;
			case TYPE_ARRAY:
				attribute = getAttribute();
				Object[] arrayElement = Helper.getArrayOfValue(attribute, eb,
						params);

				if (arrayElement != null && arrayElement.length > 0) {
					String app = "";
					String sep = "";

					for (int i = 0; i < arrayElement.length; i++) {
						app += sep + "?";
						sep = ",";
					}
					ret = app;
				} else {
					ret = getSql(variables);
				}
				break;
			}
			return ret;
		} else {
			StringBuffer sb = new StringBuffer();				
			
			switch (type) {
			case TYPE_SIMPLE:
				sb.append(getSql(variables));
				break;
			case TYPE_IF:

				if (definition.existsCondition(conditionName)) {
					condition = definition.getCondition(conditionName);

					if (condition.isVerified(user, eb, params, variables)) {
						for (SqlFragment sub : subClauses) {
							sb.append(sub.getSql(definition, user, eb, params, variables));
						}
					} else {
						for (SqlFragment sub : elseSubClauses) {
							sb.append(sub.getSql(definition, user, eb, params, variables));
						}
					}
				} else {
					Log log=getLog();
					log.error("The condition " + conditionName + " does not exist");
				}
				break;
			case TYPE_ARRAY:
				attribute = getAttribute();
				Object[] arrayElement = Helper.getArrayOfValue(attribute, eb,
						params);

				if (arrayElement != null && arrayElement.length > 0) {
					String app = "";
					String sep = "";

					for (int i = 0; i < arrayElement.length; i++) {
						app += sep + "?";
						sep = ",";
					}
					sb.append(app);
				} else {
					for (SqlFragment sub : subClauses) {
						sb.append(sub.getSql(definition, user, eb, params, variables));
					}
				}
				break;
			}
			
			return sb.toString();
		}

	}

	/**
	 * Getter dell'attributo sql. Prima di restituire il codice sql prova a
	 * verificare l'impiego delle variabili all'interno del codice sql. In tal
	 * caso, sostituisce le variabili ( #[nome variabile] ) con il relativo
	 * valore.
	 * 
	 * @param variables
	 * 
	 * @return the sql
	 */
	public String getSql(Map<String, String> variables) {
		if (usedVariables.size() > 0 && variables != null) {
			// sono usate delle variabili e ci sono dei valori
			String temp = sql;
			String app;

			int i = 0;
			for (String var : usedVariables) {
				app = StringUtility.nvl(variables.get(var));
				temp = StringUtility.replace(temp, "{" + i + "}", app);
				i++;
			}
			// temp=MessageFormat.format(temp,values);

			return temp;
		} else {
			// ASSERT: non abbiamo variabili associate o non abbiamo ricevuto
			// in input la mappa delle varibili, quindi restituiamo
			// direttamente il codice sql.
			return sql;
		}
	}

	/**
	 * @return the subClauses
	 */
	public List<SqlFragment> getSubClauses() {
		return subClauses;
	}

	/**
	 * @return the type
	 */
	public SqlFragmentType getType() {
		return type;
	}

	/**
	 * @return the usedVariables
	 */
	public List<String> getUsedVariables() {
		return usedVariables;
	}

	/**
	 * Getter dell'attributo conditionedClause.
	 * 
	 * @return the conditionedClause
	 */
	public boolean isConditionedClause() {
		return type == TYPE_IF;
	}

	/**
	 * @param attribute
	 *            the attribute to set
	 */
	public void setAttribute(SqlParameter attribute) {
		type = SqlFragmentType.TYPE_ARRAY; // imposta il tipo
		this.attribute = attribute;
	}

	/**
	 * Setter dell'attributo conditionName.
	 * 
	 * @param value
	 *            the conditionName to set
	 */
	public void setConditionName(String value) {
		type = SqlFragmentType.TYPE_IF; // imposta il tipo
		value = value.toUpperCase();
		this.conditionName = value;
	}

	/**
	 * @param elseParameters
	 *            the elseParameters to set
	 */
	public void setElseParameters(List<SqlParameter> elseParameters) {
		this.elseParameters = elseParameters;
	}

	/**
	 * @param elseSql
	 *            the elseSql to set
	 */
	public void setElseSql(String elseSql) {
		this.elseSql = elseSql;
	}

	/**
	 * setter
	 * 
	 * @param value
	 */
	public void setElseSubClauses(List<SqlFragment> value) {
		this.elseSubClauses = value;
	}

	/**
	 * Setter dell'attributo parameters.
	 * 
	 * @param parameters
	 *            the parameters to set
	 */
	public void setParameters(List<SqlParameter> parameters) {
		this.parameters = parameters;
	}

	/**
	 * Setter dell'attributo sql.
	 * 
	 * @param sql
	 *            the sql to set
	 */
	public void setSql(String sql) {
		this.sql = sql;
	}

	/**
	 * @param subClauses
	 *            the subClauses to set
	 */
	public void setSubClauses(List<SqlFragment> subClauses) {
		this.subClauses = subClauses;
	}

	/**
	 * @param type
	 *            the type to set
	 */
	public void setType(SqlFragmentType type) {
		this.type = type;
	}

	/**
	 * Inizializza il codice sql e gli array di parametri
	 */
	protected void setUp(SqlFragmentType tipo) {
		type = tipo;

		sql = "";
		parameters = new ArrayList<SqlParameter>();

		elseSql = "";
		elseParameters = new ArrayList<SqlParameter>();

		subClauses = new ArrayList<SqlFragment>();
		elseSubClauses = new ArrayList<SqlFragment>();

		usedVariables = new ArrayList<String>();
	}

	/**
	 * @param usedVariables
	 *            the usedVariables to set
	 */
	public void setUsedVariables(ArrayList<String> usedVariables) {
		this.usedVariables = usedVariables;
	}
}
